﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DDD {
    /// <summary>
    /// シーングラフの頂点のノード
    /// </summary>
    /// <remarks>
    /// シーングラフの頂点のノードは必ずこの<see cref=" World"/>クラスになります。
    /// <para>
    /// シーングラフ
    /// </para>
    /// <para>
    /// シーンのフリーズ
    /// </para>
    /// </remarks>
    public class World : Node, IEnumerable<Node> {

        #region Field
        Node activeCamera;
        bool freezed;
        Node[] nodes;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        public World () {
            this.activeCamera = null;
            this.freezed = false;
            this.nodes = null;
        }
        #endregion

        #region Property
        /// <summary>
        /// アクティブなカメラ･ノード
        /// </summary>
        public Node ActiveCamera {
            get { return activeCamera; }
        }

        /// <summary>
        /// スカイボックス
        /// </summary>
        public Skybox SkyBox {
            get { return GetComponent<Skybox> (); }
        }

        /// <summary>
        /// フリーズされたかどうかのフラグ
        /// </summary>
        /// <remarks>
        /// 一部のメソッドはシーンがフリーズした状態でないと呼べません
        /// </remarks>
        public new bool IsFreezed {
            get { return freezed; }
        }

        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。同一のオブジェクトが複数出現する可能性があります。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                var objs = base.References.ToList ();
                objs.Add (activeCamera);
                objs.RemoveAll (x => x == null);
                return objs;
            }
        }
        #endregion


        #region Method


        /// <summary>
        /// アクティブなカメラをセットします
        /// </summary>
        /// <remarks>
        /// シーンをレンダリングするためにはシーンの中に存在するカメラの中から1つを選んで
        /// アクティブにする必要があります。
        /// </remarks>
        /// <param name="node">カメラ(ノード)</param>
        public void SetActiveCamera (Node node) {
            if (node != null && !node.Is<Camera> ()) {
                throw new ArgumentException ("Not Camera node");
            }
            if (node != null && node.Root != this) {
                throw new InvalidOperationException ("Camera is not in the this Scene");
            }

            this.activeCamera = node;
        }

        /// <summary>
        /// シーンをアップデートする
        /// </summary>
        /// <remarks>
        /// シーンの全ノードをUpdate()します。
        /// 呼び出し順は<see cref="Attachable.ExecutionOrder"/>の順です。
        /// さらに各ノードはアタッチされた全てのコンポーネントのOnUPdate()メソッドを呼び出します。
        /// この関数はシーンをフリーズしている時のみ呼び出せます。
        /// </remarks>
        /// <param name="msec">ワールド時刻(in msec)</param>
        public new void Update (int msec) {
            //if (!IsFreezed) {
            //    throw new InvalidOperationException ("Scene is not freezed");
            //}

            var nodes = from n in Downwards
                        orderby n.ExecutionOrder
                        select n;

            foreach (var n in nodes) {
                n.Update (msec);
            }
        }

        /// <summary>
        /// シーンのLODレベルを選択する
        /// </summary>
        /// <remarks>
        /// シーンの全ノードをSelect()します。
        /// もしノードに<see cref="LODSelector"/>コンポーネントがアタッチされていれば
        /// LOD選択処理を行い1つの子ノードのみを有効化します。
        /// </remarks>
        public new void Select () {
            if (!IsFreezed) {
                throw new InvalidOperationException ("Scene is not freezed");
            }

        }

        /// <summary>
        /// シーン全体のアライメントを調整します
        /// </summary>
        /// <remarks>
        /// シーンの全ノードをAlign()します。
        /// もしノードにアライメント設定がセットされていた場合
        /// アライメント処理を行い指定の方角を向くように開店させます。
        /// </remarks>
        public new void Align () {
            if (!IsFreezed) {
                throw new InvalidOperationException ("Scene is not freezed");
            }

        }

        /// <summary>
        /// シーン全体のUniform変数のリフレッシュを行います
        /// </summary>
        public new void Reflesh () {
            if (!IsFreezed) {
                throw new InvalidOperationException ("Scene is not freezed");
            }

        }

        /// <summary>
        /// シーン全体から指定のレイを使ってノードをピックします
        /// </summary>
        /// <param name="ray">ピック光線(ワールド座標)</param>
        /// <returns></returns>
        /// <remarks>
        /// シーンの全ノードをPick()します。
        /// このメソッドはフリーズ状態の時のみ呼び出し可能です。
        /// </remarks>
        public new RayIntersection Pick (Ray ray) {
            return new RayIntersection ();
        }


        /// <summary>
        /// シーンをフリーズします
        /// </summary>
        /// <remarks>
        /// シーンをフリーズして変換行列やコリジョンなどを計算しキャッシュします。
        /// 一部の関数はフリーズしないと呼び出せません。
        /// </remarks>
        public void Freeze () {
            this.nodes = Downwards.ToArray ();
            this.freezed = true;
        }

        /// <summary>
        /// シーンのフリーズを解除します
        /// </summary>
        /// <remarks></remarks>
        public void DeFreeze () {
            this.nodes = null;
            this.freezed = false;
        }

        /// <summary>
        /// 全ノードを列挙する列挙子
        /// </summary>
        /// <returns></returns>
        IEnumerator<Node> IEnumerable<Node>.GetEnumerator () {
            if (freezed) {
                return ((IEnumerable<Node>)nodes).GetEnumerator ();
            }
            return Downwards.GetEnumerator ();
        }

        /// <summary>
        /// 全ノードを列挙する列挙子
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
            if (freezed) {
                return nodes.GetEnumerator ();
            }
            return Downwards.GetEnumerator ();
        }

        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>現在のオブジェクトを説明する文字列</returns>
        public override string ToString () {
            return string.Format ("World: Nodes={0}, Freezed={1}", Downwards.Count (), IsFreezed);
        }
        #endregion

    }
}
